iT邦幫忙

2024 iThome 鐵人賽

0
自我挑戰組

前端工程師的java學習紀錄系列 第 32

Day32-線程Thread(二)

  • 分享至 

  • xImage
  •  

多個線程同時對一個數據進行操作時,有可能會發生線程不安全的問題,這時候要使用synchronized 關鍵字將線程同步。

💡線程不安全的舉例:
今天有10張票券在進行銷售,同時有3個銷售人員在販售(線程),但是它們彼此之間不會知道誰已經銷售出去了(不同步),這時有可能A銷售人員去確認剩餘票券時,看到還有1張票,接著回去跟客人收錢(尚未將票拿走),B銷售人員接著也跑去確認剩餘票券,看到還有1張票,接著回去跟客人收錢(尚未將票拿走),這時A跟B同時都跟客人收了錢,但是只剩下1張票,票應該給誰呢?

同步作用域

synchronized(同步監視器) {
	//需要被同步的程式碼
}

當線程執行在synchronized 中的程式碼時,會強迫其他使用到這個程式碼的線程等待,直到它完成,同步監視器又稱為 ,同步監視器必須 使用唯一的物件,不限是甚麼類型

class SaleTicket implements Runnable {
	int ticket = 100;
	
	Object obj = new Object();
	@Override
	public void run() {
	
		while(true) {
		//也可以寫obj
//		synchronized(obj) {
			synchronized(this) {
			
				if(tickek > 0) {
					System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
				} else {
					break;
				}
				
			}
		}
		
	}
	
}

public class SaleTicket {

	public static void main(String[] args) {
	
		SaleTicket saleTicket = new SaleTicket();
		
		Thread t1 = new Thread(saleTicket);
		Thread t2 = new Thread(saleTicket);
		Thread t3 = new Thread(saleTicket);
						
		t1.start();
		t2.start();
		t3.start();
		
	}
}

使用接口的方式進行線程的創建時,會是用多態 的概念將線程實例化,所以這邊可以使用this 當作synchronized 的參數


使用繼承的方式

class SaleTicket extends Thread {
	int ticket = 100;
	
	static Object obj = new Object();
	@Override
	public void run() {
	
		while(true) {
		//也可以寫obj
//		synchronized(obj) {
//			💡不可以寫this,因為會有3個SaleTicket的實例物件
//		synchronized(this) {
//    Window.class會在反射的時候提到,相當於是Class clz = Window.class,記憶體中的Window類
			synchronized(Window.class) {
				if(tickek > 0) {
					System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
				} else {
					break;
				}
				
			}
		}
		
	}
	
}

public class SaleTicket {

	public static void main(String[] args) {
	
		SaleTicket s1 = new SaleTicket();
		SaleTicket s2 = new SaleTicket();
		SaleTicket s3 = new SaleTicket();
						
		s1.start();
		s2.start();
		s3.start();
		
	}
}

同步方法

實作接口

class SaleTicket implements Runnable {
	int ticket = 100;
	
	boolean isFlag = true;
	@Override
	public void run() {
	
		while(isFlag) {
			show();
		}
	}
	
	// 可以將方法改為同步的
	public synchronized void show() {
		if(tickek > 0) {
					System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
				} else {
					isFlag = flase;
				}
		}
}

public class SaleTicket {

	public static void main(String[] args) {
	
		SaleTicket saleTicket = new SaleTicket();
		
		Thread t1 = new Thread(saleTicket);
		Thread t2 = new Thread(saleTicket);
		Thread t3 = new Thread(saleTicket);
						
		t1.start();
		t2.start();
		t3.start();
		
	}
}

implements Runnable 接口時,synchronized方法時,同步監視器會自動的使用this ,這時不用擔心會出現非唯一 的問題發生。


使用繼承

class SaleTicket extends Thread {
	int ticket = 100;
	
	static Object obj = new Object();
	@Override
	public void run() {
	
		while(isFlag) {
			show();
		}
	}
	
	//由於有3個實例,所以synchronized 指向的是各實例化的物件,這樣無法成功
	//public synchronized void show() {
	//可以使用static將方法轉為靜態方法,但是應該視情況而定,不要為了使用synchronized而加上static
		public static synchronized void show() {
		if(tickek > 0) {
					System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
				} else {
					isFlag = flase;
				}
		}
}

public class SaleTicket {

	public static void main(String[] args) {
	
		SaleTicket s1 = new SaleTicket();
		SaleTicket s2 = new SaleTicket();
		SaleTicket s3 = new SaleTicket();
						
		s1.start();
		s2.start();
		s3.start();
		
	}
}

使用繼承 的方式時,由於每一個線程都是有自己的實例化 這時候synchronized指向的會是自身實例化的物件所以非唯一,可以使用static將方法轉為靜態方法,但是應該視情況而定,不要為了使用synchronized而加上static


上一篇
Day31-線程Thread(一)
下一篇
Day33-線程Thread(三)
系列文
前端工程師的java學習紀錄38
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言